#ifdef __cplusplus
extern "C" {
#endif

#include <stdarg.h> /* va_list, va_start(), va_end() */
#include <stdio.h>  /* __printf()'s */
#include <string.h> /* strchr(), memmove() */
#include <stdlib.h> /* malloc(), free(), realloc() */
#include <errno.h>  // errno()

#include <sys/types.h>
#include <unistd.h> /* getpid() */

#ifdef __cplusplus
}
#endif
#include "rcs_print.hh"
#include "linklist.hh"
#ifndef _TIMER_H
extern "C" double etime(void);
#endif
#include "log/zuclog.h"

LinkedList* rcs_print_list = NULL;
char** rcs_lines_table = NULL;
void (*rcs_print_notify)() = NULL;
RCS_PRINT_DESTINATION_TYPE rcs_print_destination = RCS_PRINT_TO_STDOUT;

int max_rcs_errors_to_print = 30;
int rcs_errors_printed = 0;

long rcs_print_mode_flags = PRINT_RCS_ERRORS;
FILE* rcs_print_file_stream = NULL;
char rcs_print_file_name[80] = "rcs_out.txt";

char last_error_bufs[4][100];
int error_bufs_initialized = 0;
int last_error_buf_filled = 0;

void set_rcs_print_destination(RCS_PRINT_DESTINATION_TYPE _dest)
{
    if (rcs_print_destination == RCS_PRINT_TO_NULL)
    {
        rcs_errors_printed = 0;
    }
    rcs_print_destination = _dest;
}

RCS_PRINT_DESTINATION_TYPE get_rcs_print_destination() { return (rcs_print_destination); }

char** get_rcs_lines_table() { return (rcs_lines_table); }

LinkedList* get_rcs_print_list() { return (rcs_print_list); }

int get_rcs_print_list_size()
{
    if (NULL != rcs_print_list)
    {
        return (rcs_print_list->list_size);
    }
    else
    {
        return (-1);
    }
}
void set_rcs_print_list_sizing(int _new_max_size, LIST_SIZING_MODE _new_sizing_mode)
{
    if (NULL == rcs_print_list)
    {
        rcs_print_list = new LinkedList;
    }
    if (NULL != rcs_print_list)
    {
        rcs_print_list->set_list_sizing_mode(_new_max_size, _new_sizing_mode);
    }
}
void set_rcs_print_notify(RCS_PRINT_NOTIFY_FUNC_PTR _rcs_print_notify) { rcs_print_notify = _rcs_print_notify; }

void clean_print_list()
{
    if (NULL != rcs_print_list)
    {
        delete rcs_print_list;
        rcs_print_list = NULL;
    }
}

void output_print_list(int output_func(char*))
{
    if (NULL != rcs_print_list)
    {
        char* string_from_list;
        string_from_list = (char*)rcs_print_list->get_head();
        while (NULL != string_from_list)
        {
            if (output_func(string_from_list) != EOF)
            {
                break;
            }
            string_from_list = (char*)rcs_print_list->get_next();
        }
    }
}

int count_characters_in_print_list()
{
    int count = 0;
    if (NULL != rcs_print_list)
    {
        char* string_from_list;
        string_from_list = (char*)rcs_print_list->get_head();
        while (NULL != string_from_list)
        {
            count += strlen(string_from_list);
            string_from_list = (char*)rcs_print_list->get_next();
        }
    }
    return (count);
}

int count_lines_in_print_list()
{
    int count = 1;
    if (NULL != rcs_print_list)
    {
        char* string_from_list;
        string_from_list = (char*)rcs_print_list->get_head();
        while (NULL != string_from_list)
        {
            char* line;
            line = strchr(string_from_list, '\n');
            while (NULL != line)
            {
                count++;
                line = strchr(line + 1, '\n');
            }
            string_from_list = (char*)rcs_print_list->get_next();
        }
    }
    return (count);
}

void convert_print_list_to_lines()
{
    char* temp_buf = NULL;
    static int last_id_converted = -1;
    if (NULL != rcs_print_list)
    {
        char* string_from_list;
        if (-1 == last_id_converted)
        {
            string_from_list = (char*)rcs_print_list->get_head();
        }
        else
        {
            string_from_list = (char*)rcs_print_list->get_first_newer(last_id_converted);
        }
        while (NULL != string_from_list)
        {
            char* next_line;
            next_line = strchr(string_from_list, '\n');
            if (NULL == next_line)
            {
                if (NULL == temp_buf)
                {
                    temp_buf = (char*)malloc(strlen(string_from_list) + 1);
                    strcpy(temp_buf, string_from_list);
                }
                else
                {
                    temp_buf = (char*)realloc(temp_buf, strlen(temp_buf) + strlen(string_from_list) + 1);
                    strcat(temp_buf, string_from_list);
                }
                rcs_print_list->delete_current_node();
            }
            else
            {
                if (temp_buf != NULL)
                {
                    temp_buf = (char*)realloc(temp_buf, strlen(temp_buf) + strlen(string_from_list) + 1);
                    strcat(temp_buf, string_from_list);
                    rcs_print_list->delete_current_node();
                    rcs_print_list->store_after_current_node(temp_buf, strlen(temp_buf) + 1, 1);
                    free(temp_buf);
                    temp_buf = NULL;
                }
                else if (next_line[1] != 0)
                {
                    rcs_print_list->store_after_current_node(next_line + 1, strlen(next_line + 1) + 1, 1);
                    next_line[1] = 0;
                }
            }
            string_from_list = (char*)rcs_print_list->get_next();
        }
    }
    last_id_converted = rcs_print_list->get_newest_id();
    if (temp_buf != NULL)
    {
        rcs_print_list->store_at_tail(temp_buf, strlen(temp_buf) + 1, 1);
        free(temp_buf);
        temp_buf = NULL;
    }
}

void update_lines_table()
{
    if (NULL != rcs_lines_table)
    {
        free(rcs_lines_table);
        rcs_lines_table = NULL;
    }
    if (NULL != rcs_print_list)
    {
        convert_print_list_to_lines();
        rcs_lines_table = (char**)malloc(sizeof(char*) * rcs_print_list->list_size);
        if (NULL != rcs_print_list)
        {
            char* string_from_list;
            string_from_list = (char*)rcs_print_list->get_head();
            int i = 0;
            while (NULL != string_from_list)
            {
                rcs_lines_table[i] = string_from_list;
                i++;
                string_from_list = (char*)rcs_print_list->get_next();
            }
        }
    }
}

char* strip_control_characters(char* _dest, char* _src)
{
    static char line_buffer[255];
    char* destination;
    char* control_char_loc;
    if (NULL == _dest)
    {
        destination = line_buffer;
        if (strlen(_src) < 255)
        {
            strcpy(line_buffer, _src);
        }
        else
        {
            if (NULL == strpbrk(_src, "\n\r\t\b"))
            {
                return (_src);
            }
            else
            {
                return (NULL);
            }
        }
    }
    else
    {
        destination = _dest;
        if (_dest != _src)
        {
            memmove(_dest, _src, strlen(_src));
        }
    }
    control_char_loc = strpbrk(destination, "\n\r\t\b");
    while (NULL != control_char_loc)
    {
        *control_char_loc = ' '; /* Replace control character with
					   SPACE */
        control_char_loc = strpbrk(control_char_loc, "\n\r\t\b");
    }
    return (destination);
}

/* In windows DLLs for Microsoft Visual C++ sscanf is not supported so
use separate_words to parse the string followed by commands like strtod to
convert each word. */
int separate_words(char** _dest, int _max, char* _src)
{
    static char word_buffer[256];
    int i;
    if (NULL == _dest || NULL == _src)
    {
        return -1;
    }
    if (strlen(_src) > 255)
    {
        return -1;
    }
    strcpy(word_buffer, _src);
    _dest[0] = strtok(word_buffer, " \n\r\t");
    for (i = 0; NULL != _dest[i] && i < _max - 1; i++) { _dest[i + 1] = strtok(NULL, " \n\r\t"); }
    if (_dest[_max - 1] == NULL && i == _max - 1)
    {
        i--;
    }
    return (i + 1);
}

int rcs_vprint(const char* _fmt, va_list _args, int save_string)
{
    static char temp_string[256];

    if (NULL == _fmt)
    {
        return (EOF);
    }
    if (strlen(_fmt) > 200)
    { /* Might overflow temp_string. */
        return (EOF);
    }
    if (EOF == (int)vsnprintf(temp_string, sizeof(temp_string), _fmt, _args))
    {
        return (EOF);
    }
    if (save_string)
    {
        if (!error_bufs_initialized)
        {
            memset(last_error_bufs[0], 0, 100);
            memset(last_error_bufs[1], 0, 100);
            memset(last_error_bufs[2], 0, 100);
            memset(last_error_bufs[3], 0, 100);
            error_bufs_initialized = 1;
        }
        last_error_buf_filled++;
        last_error_buf_filled %= 4;
        strncpy(last_error_bufs[last_error_buf_filled], temp_string, 99);
    }
    return (rcs_fputs(temp_string));
}

int rcs_puts(const char* _str)
{
    int retval, retval2;
    retval = rcs_fputs(const_cast<char*>(_str));
    if (retval != EOF)
    {
        retval2 = rcs_fputs(const_cast<char*>("\n"));
        if (retval2 != EOF)
        {
            retval += retval;
        }
        else
        {
            retval = EOF;
        }
    }
    return (retval);
}

int rcs_fputs(const char* _str)
{
    int retval = EOF;
    if (NULL != _str)
    {
        if (0 == _str[0])
        {
            return (0);
        }
        switch (rcs_print_destination)
        {
        case RCS_PRINT_TO_LOGGER:

        case RCS_PRINT_TO_STDOUT:
            retval = fputs(_str, stdout);
            fflush(stdout);
            break;

        case RCS_PRINT_TO_STDERR:
            retval = fputs(_str, stderr);
            fflush(stderr);
            break;

        case RCS_PRINT_TO_LIST:
            if (NULL == rcs_print_list)
            {
                rcs_print_list = new LinkedList;
                if (NULL != rcs_print_list)
                {
                    rcs_print_list->set_list_sizing_mode(256, DELETE_FROM_HEAD);
                }
            }
            if (NULL != rcs_print_list)
            {
                if (-1 == rcs_print_list->store_at_tail((void*)_str, (retval = strlen(_str)) + 1, 1))
                {
                    retval = EOF;
                }
            }
            break;
        case RCS_PRINT_TO_NULL:
            retval = strlen(_str);
            break;
        case RCS_PRINT_TO_FILE:
            if (NULL == rcs_print_file_stream)
            {
                if (NULL == rcs_print_file_name)
                {
                    return EOF;
                }
                rcs_print_file_stream = fopen(rcs_print_file_name, "a+");
            }
            if (NULL == rcs_print_file_stream)
            {
                return EOF;
            }
            retval = fputs(_str, rcs_print_file_stream);
            fflush(rcs_print_file_stream);
            break;

        default:
            break;
        }
        if (NULL != rcs_print_notify)
        {
            (*rcs_print_notify)();
        }
    }
    return (retval);
}

void close_rcs_printing()
{
    switch (rcs_print_destination)
    {
    case RCS_PRINT_TO_LIST:
        clean_print_list();
        break;

    case RCS_PRINT_TO_FILE:
        if (NULL != rcs_print_file_stream)
        {
            fclose(rcs_print_file_stream);
            rcs_print_file_stream = NULL;
        }
        break;
    default:
        break;
    }
    return;
}

int set_rcs_print_file(char* _file_name)
{
    if (_file_name == NULL)
    {
        return -1;
    }
    if (strlen(_file_name) > 80)
    {
        return -1;
    }
    strcpy(rcs_print_file_name, _file_name);
    if (NULL != rcs_print_file_stream)
    {
        fclose(rcs_print_file_stream);
    }
    rcs_print_file_stream = fopen(rcs_print_file_name, "a+");
    if (NULL == rcs_print_file_stream)
    {
        return -1;
    }
    return 0;
}

int rcs_print(const char* _fmt, ...)
{
    char buf[2048] = {0};
    va_list ap;
    va_start(ap, _fmt);
    int s = vsnprintf(buf, 2048 - 1, _fmt, ap);
    va_end(ap);
    zlog_info("%s", buf);
    return s;
}

#ifndef DO_NOT_USE_RCS_PRINT_ERROR_NEW
static const char* rcs_error_filename = NULL;
static int rcs_error_linenum = -1;
int set_print_rcs_error_info(const char* file, int line)
{
    rcs_error_filename = file;
    rcs_error_linenum = line;
    return (0);
}

int print_rcs_error_new(const char* _fmt, ...)
{
    int retval = 0;
    va_list args;
    va_start(args, _fmt);
    if ((rcs_print_mode_flags & PRINT_RCS_ERRORS) && ((max_rcs_errors_to_print >= rcs_errors_printed) || max_rcs_errors_to_print < 0))
    {
        if (NULL != rcs_error_filename && rcs_error_linenum > 0)
        {
            rcs_print("%s %d: ", rcs_error_filename, rcs_error_linenum);
            rcs_error_filename = NULL;
            rcs_error_linenum = -1;
        }
        retval = rcs_vprint(_fmt, args, 1);
        if (max_rcs_errors_to_print == rcs_errors_printed && max_rcs_errors_to_print >= 0)
        {
            rcs_print("\nMaximum number of errors to print exceeded!\n");
        }
    }
    if (rcs_print_destination != RCS_PRINT_TO_NULL)
    {
        rcs_errors_printed++;
    }
    va_end(args);
    return (retval);
}

#endif

int rcs_print_debug(long flag_to_check, const char* _fmt, ...)
{
    int retval = 0;
    int pid = 0;
    va_list args;
    va_start(args, _fmt);

    if (flag_to_check & rcs_print_mode_flags)
    {
        pid = getpid();
        rcs_print("(time=%f,pid=%d): ", etime(), pid);
        retval = rcs_vprint(_fmt, args, 0);
    }
    va_end(args);
    return (retval);
}

void set_rcs_print_flag(long flag_to_set) { rcs_print_mode_flags |= flag_to_set; }

void clear_rcs_print_flag(long flag_to_clear) { rcs_print_mode_flags &= ~(flag_to_clear); }

int rcs_print_sys_error(int error_source, const char* _fmt, ...)
{
    static char temp_string[256];
    static char message_string[512];
    va_list args;
    int r;

    if (NULL == _fmt)
    {
        return (EOF);
    }
    if (strlen(_fmt) > 200)
    { /* Might overflow temp_string. */
        return (EOF);
    }

    va_start(args, _fmt);
    r = vsnprintf(temp_string, sizeof(temp_string), _fmt, args);
    va_end(args);

    if (r < 0)
    {
        return EOF;
    }

    if (max_rcs_errors_to_print == rcs_errors_printed && max_rcs_errors_to_print >= 0)
    {
        rcs_print("\nMaximum number of errors to print exceeded!\n");
    }
    rcs_errors_printed++;
    if (max_rcs_errors_to_print <= rcs_errors_printed && max_rcs_errors_to_print >= 0)
    {
        return (EOF);
    }

    switch (error_source)
    {
    case ERRNO_ERROR_SOURCE:
        snprintf(message_string, sizeof(message_string), "%s %d %s\n", temp_string, errno, strerror(errno));
        rcs_puts(message_string);
        break;

    default:
        rcs_puts(temp_string);
        break;
    }
    return (strlen(temp_string));
}

#ifdef rcs_print_error
#undef rcs_print_error
#endif
extern "C" int rcs_print_error(const char* _fmt, ...);

int rcs_print_error(const char* _fmt, ...)
{
    int retval = 0;
    va_list args;

    va_start(args, _fmt);
    if ((rcs_print_mode_flags & PRINT_RCS_ERRORS) && ((max_rcs_errors_to_print >= rcs_errors_printed) || max_rcs_errors_to_print < 0))
    {
        retval = rcs_vprint(_fmt, args, 1);
        if (max_rcs_errors_to_print == rcs_errors_printed && max_rcs_errors_to_print >= 0)
        {
            rcs_print("\nMaximum number of errors to print exceeded!\n");
        }
    }
    if (rcs_print_destination != RCS_PRINT_TO_NULL)
    {
        rcs_errors_printed++;
    }
    va_end(args);
    return (retval);
}
